Analysis on the Crowd4SDG Slack included in the Report
library("RColorBrewer") #Color Palette
library("wordcloud") #Wordcloud Library
library("jsonlite") #Handling JSON Files
library("ggplot2") #Plots
library("igraph") #Networks and Analysis
library("scales") #Plot Aesthetics
library("visNetwork") #D3 based Network Plots
library("plotly") #Plots
library("dplyr") #Dataframe Manipulations
library("qgraph") #Network analyis and Plots
library("networkD3") #Sankey
source("custom.R") #Some Custom Functions (courtesy Marc)
users_df = read.csv("data_files/users_an.csv") #Users Meta
channels_df = read.csv("data_files/channels.csv") #Channels Meta
df_emoticon_responses = read.csv("data_files/emoticon_responses.csv")[,-c(1)] #Emoticon Responses
df_text_mentions = read.csv("data_files/text_mentions.csv")[,-c(1)] #Text Mentions
df_texts = read.csv("data_files/texts_channel.csv")[,-c(1)] #Messages
df_channel_join = read.csv("data_files/channel_join.csv")[,-c(1)] #Channel Join Messages
df_threads = read.csv("data_files/threads_channel.csv")[,-c(1)] #Threads
df_th_rep = read.csv("data_files/thread_replies.csv")[,c(-1)] #Thread Replies
teams = c("team-aquatics", "team-floodfinder", "team-toseetocare", "team-safewaterforhomeless", "team-wotter", "team-collaborativewatermonitoring", "teamfloodmaps", "team-filsteiner", "team-mnl", "team-rainwaterharvesting", "team-garudasavior", "team-safeskye", "team-fews", "team-rainwater-collection", "team-potamoi", "team-dailywater", "team_ecolution", "team-thousand-waters", "warbon-footprint")
Plot Functions
#Toggle for arrowheads if directed
visPlot_ni = function(subgraph, communities = rep(1, length(V(subgraph))), nodesize = 10, edgewidth = 0, title="", textsize = nodesize, layout = "layout_nicely", directed = TRUE)
{
nodes <- data.frame(id = V(subgraph)$name, name = V(subgraph)$print, group = communities)
nodes$font.size<-textsize + 20
nodes$font.color = "grey"
nodes$font.face = "calibri"
nodes$label = nodes$name
nodes$value = nodesize
edges <- data.frame(get.edgelist(subgraph))
edges$width = edgewidth
colnames(edges)<-c("from","to","width")
plt = visNetwork(nodes, edges, height = "1000px", width = "100%",main = title)%>%
visIgraphLayout(layout = layout) %>%
visPhysics(solver = "repulsion", hierarchicalRepulsion = "nodeDistance") %>%
visOptions(highlightNearest = TRUE) %>%
visNodes(scaling = list(min = 10, max = 30)) %>%
visEdges(smooth = T, color = list(color = "lightblue", highlight = "blue")) %>%
visGroups(groupname = "2", color = list(background = "lightgray",border = "black")) %>%
visInteraction(keyboard = T,
dragNodes = T,
dragView = T,
zoomView = T)
if (directed == TRUE)
plt = plt %>% visEdges(arrows ="to")
return(plt)
}
#Plot Function with qgraph library
plotNetwork <- function(Gsub, layout='default',maine='', coms=NULL, teams = NULL){
if (is.null(coms)){
com <- cluster_walktrap(Gsub)
V(Gsub)$color <- com$membership+1
} else {
#col = merge(data.frame(team = coms), list)
V(Gsub)$color <- coms
}
vertex.frame.color <- V(Gsub)$color
size <- abs(strength(Gsub, loops=T))
if (max(size, na.rm=T)>0){
size <- 30 * size / max(size, na.rm=T)
} else {
size <- 1
}
if (is.null(E(Gsub)$weight)){
width <- 1
edge.color <- NA
l <- layout_with_fr
} else{
weight <- abs(E(Gsub)$weight)
width <- .01 + 5 * weight / max(weight, na.rm=T)
edge.color <- ifelse(E(Gsub)$weight > 0, 'gray', 'blue')
e <- get.edgelist(Gsub, names=F)
e <- cbind(e, E(Gsub)$weight)
l <- qgraph.layout.fruchtermanreingold(e,
vcount=vcount(Gsub),
area=1*(vcount(Gsub)^2),
repulse.rad=5*(vcount(Gsub)^2))
}
if (layout!='default'){
l <- layout
}
plot(Gsub,
layout=l,
edge.width = width,
edge.color=edge.color,
vertex.label = V(Gsub)$Name,
vertex.label.color = "black",
vertex.label.cex = 0.7,
vertex.size = 3 * sqrt(size),
edge.curved=0.1,
vertex.frame.color = vertex.frame.color,
edge.arrow.width = 0.1,
edge.arrow.size = 0.1,
# main=maine,
# main.cex = 0.3,
col='gray'
)
title(main = maine , cex.main = 0.7)
return(l)
}
Sort Activity by Week
Sys.setenv(TZ="GMT")
df_texts$source_message_timestamp = as.POSIXct(df_texts$source_message_timestamp, origin = "1970-01-01")
df_channel_activity_weekly = data.frame()
for (i in unique(df_texts$channel))
{
if (T)
{
obj = hist(df_texts$source_message_timestamp[df_texts$channel == i], breaks = "weeks", plot = F)
for (j in 1:length(obj$counts))
{
df_channel_activity_weekly = rbind(df_channel_activity_weekly, data.frame(channel = i, activity = obj$counts[j], weeks = obj$breaks[j]))
}
}
}
Wordclouds
list = sort(unique(df_channel_activity_weekly$weeks))
weeks = list
wc = list()
dir.create('viz', showWarnings = F)
for (i in list[1:(length(list))])
{
set.seed(1234)
# print(as.POSIXct(i, origin = "1970-01-01"))
pdf(paste0('viz/wordclouds_week',match(i,list),'.pdf'), 10, 10)
# par(mfrow=c(3,3))
wc = wordcloud(words = df_channel_activity_weekly$channel[df_channel_activity_weekly$weeks == i],
freq = df_channel_activity_weekly$activity[df_channel_activity_weekly$weeks == i],
min.freq = 0,max.words=100, random.order=FALSE, rot.per=0, colors=brewer.pal(8, "Dark2"))
dev.off()
}
Timezones
plt = ggplot(users_df, aes(x = reorder(timezone,timezone,
function(x)-length(x)))) +
geom_bar(stat = "count") +
theme_bw() +
# theme(axis.text.x = element_text(angle = 90,hjust=0.95,vjust=0.2)) +
theme(axis.text.x = element_text(angle = 45,hjust=1,vjust=1)) +
ylab("Number of Users") +
# scale_y_log10() +
ggtitle("Users Timezones" ) +
theme(legend.position = "none") +
xlab("Timezones")
ggplotly(plt)
ggsave('viz/timezones.pdf', width = 7, height = 4)
Channel activity
# tot = aggregate(df_channel_activity_monthly$activity, by = list(channel =df_channel_activity_monthly$channel), FUN = sum)
tb = table(df_texts$channel)
tot = data.frame('channel'=names(tb), x = as.numeric(tb))
# tot$x = tot$x/sum(tot$x)
plt = ggplot(tot, aes(x=reorder(channel,x), y = x)) +
geom_bar(stat = "identity") + theme_bw(base_size = 15) +
# theme(axis.text.x = element_text(face = "bold", angle = 90)) +
theme(axis.text.x = element_text(angle = 45,hjust=1,vjust=1)) +
xlab("") + ylab("Number of Posts") + ggtitle("Total number of posts" ) + theme(legend.position = "none") + coord_flip()
#+ scale_y_log10()
ggplotly(plt)
ggsave('viz/channels.pdf', width = 7, height = 8)
Country of Origin
Sankey Location
country = read.csv("data_files/participants.csv", stringsAsFactors = FALSE)
nodes = data.frame(Name = union(country$Nationality, country$Team.Name))
links = country[,c("Team.Name", "Nationality")] %>% group_by(Team.Name, Nationality) %>% summarise(count = n())
`summarise()` has grouped output by 'Team.Name'. You can override using the `.groups` argument.
links$IDsource <- match(links$Team.Name, nodes$Name)-1
links$IDtarget <- match(links$Nationality, nodes$Name)-1
plt = sankeyNetwork(Links = links, Nodes = nodes,
Source = "IDsource", Target = "IDtarget",
Value = "count", NodeID = "Name",
sinksRight=FALSE, fontSize = 13)
Links is a tbl_df. Converting to a plain data frame.
plt
visSave(plt, 'viz/sankey_nationalities.html')
#ggsave('viz/sankey_nationality.pdf', height=5, width=10)
Sankey Timezone
org_team_part = users_df[!users_df$Team %in% c("Organiser", "Mentor", "Project Partner", "NO IDEA", "External Resource"),]
org_team_part = org_team_part[!grepl("Dropped Out", org_team_part$Team),]
#org_team_part$Team = as.character(org_team_part$Team)
nodes = data.frame(Name = union(org_team_part$Team, org_team_part$timezone))
nodes$Name = as.factor(nodes$Name)
links = org_team_part[,c("Team", "timezone")] %>% group_by(Team, timezone) %>% summarise(count = n())
`summarise()` has grouped output by 'Team'. You can override using the `.groups` argument.
links$IDsource <- match(links$Team, nodes$Name)-1
links$IDtarget <- match(links$timezone, nodes$Name)-1
plt = sankeyNetwork(Links = links, Nodes = nodes,
Source = "IDsource", Target = "IDtarget",
Value = "count", NodeID = "Name",
sinksRight=FALSE, fontSize = 13)
Links is a tbl_df. Converting to a plain data frame.
plt
visSave(plt, 'viz/sankey_timezones.html')
#ggsave('viz/sankey_timezones.pdf', height=5, width=10)
No. of Posts + Heatmap (Cut after week 6)
Channel dynamics (Heatmap of Activity)
store = function(a)
{
if (is.integer(a) & length(a)==0)
return(0)
else
return(a)
}
weeks = sort(unique(df_channel_activity_weekly$weeks))
channels = unique(df_channel_activity_weekly$channel)
channel_act_mat = matrix(0, nrow = length(channels), ncol = length(weeks))
for (i in 1:length(weeks))
{
for (j in 1:length(channels))
{
channel_act_mat[j,i] = store(df_channel_activity_weekly$activity[df_channel_activity_weekly$channel %in% channels[j] & df_channel_activity_weekly$weeks == weeks[i]])
}
}
colnames(channel_act_mat) = as.POSIXct(weeks, origin = "1970-01-01")
rownames(channel_act_mat) = channels
data <- df_channel_activity_weekly
months <- sort(unique(data$weeks))
channels <- unique(data$channel)
M <- matrix(NA,length(channels), length(weeks))
for (i in 1:nrow(data)){
M[match(data$channel[i], channels), match(data$weeks[i], weeks)] <- data$activity[i]
}
rownames(M) <- channels
colnames(M) <- paste('Week',1:ncol(M))
pdf('viz/channels_heatmap.pdf', 7,10)
peak <- apply(M,1, which.max)
M1 <- M / apply(M,1,max, na.rm=T)
M1[is.na(M1)] <- 0
heatmap.0(M1[order(peak),1:6], cexRow = 1, cexCol = 2, col=colorRampPalette(c("white","darkred")), mar=c(10,15))
dev.off()
null device
1
tot_posts <- apply(M,2,sum, na.rm=T)
pdf('viz/activity_total.pdf', 6,6)
plot.0(tot_posts, type='o',
ylab='Number of posts', xlab='Week', lwd=2)
dev.off()
null device
1
Aggregated Mentions and Reaction Networks
#Overall - mentions
text_ment1 = merge(df_text_mentions, users_df[,c("id", "Team")], by.x = "from", by.y = "id")
colnames(text_ment1)[colnames(text_ment1) == "Team"] = "From_Team"
text_ment1 = merge(text_ment1, users_df[,c("id", "Team")], by.x = "to", by.y = "id")
colnames(text_ment1)[colnames(text_ment1) == "Team"] = "To_Team"
g_grouped_mentions = graph_from_data_frame(text_ment1[,c("From_Team", "To_Team", "timestamp", "channel", "from", "to")], directed = TRUE)
E(g_grouped_mentions)$weight = 1
g_grouped_mentions = igraph::simplify(g_grouped_mentions, remove.loops = FALSE, remove.multiple = TRUE)
V(g_grouped_mentions)$print = V(g_grouped_mentions)$name
plt = visPlot_ni(g_grouped_mentions, edgewidth = rescale(E(g_grouped_mentions)$weight, to = c(1,10)), nodesize = degree(g_grouped_mentions, mode = "in"))
write.graph(g_grouped_mentions, "networks/grouped_mentions.graphml", format = "graphml")
plt
visSave(plt, 'networks/network_grouped_mention.html')
#Overall - reactions
reac_ment1 = merge(df_emoticon_responses, users_df[,c("id", "Team")], by.x = "reaction_by", by.y = "id")
colnames(reac_ment1)[colnames(reac_ment1) == "Team"] = "From_Team"
reac_ment1 = merge(reac_ment1, users_df[,c("id", "Team")], by.x = "source_message_from", by.y = "id")
colnames(reac_ment1)[colnames(reac_ment1) == "Team"] = "To_Team"
g_grouped_reactions = graph_from_data_frame(reac_ment1[,c("From_Team", "To_Team", "source_message_timestamp", "channel", "source_message_from", "reaction_by")], directed = TRUE)
E(g_grouped_reactions)$weight = 1
g_grouped_reactions = igraph::simplify(g_grouped_reactions, remove.loops = FALSE, remove.multiple = TRUE)
V(g_grouped_reactions)$print = V(g_grouped_reactions)$name
plt = visPlot_ni(g_grouped_reactions, edgewidth = rescale(E(g_grouped_reactions)$weight, to = c(1,10)), nodesize = degree(g_grouped_reactions, mode = "in"))
write.graph(g_grouped_reactions, "networks/grouped_reactions.graphml", format = "graphml")
plt
visSave(plt, 'networks/network_grouped_reactions.html')
Channel wise interaction networks
library(RColorBrewer)
#using all interactions
pdf('plotTeamNetworks.pdf')
par(mfrow=c(5,4), mar=c(0,0,2,0), cex.main=3)
df_emo = df_emoticon_responses[,c(1,2,3,5)]
colnames(df_emo) = c("From", "To", "Timestamp", "Channel")
df_emo$Type = "emoticon_responses"
df_tmt = df_text_mentions[!df_text_mentions$to %in% c("channel", "here", "everyone"),]
colnames(df_tmt) = c("From", "To", "Timestamp", "Channel")
df_tmt$Type = "text_mentions"
df_total = rbind(df_tmt, df_emo)
net_list = list()
j = 1
#channels = unique(df_total$Channel)
for (i in teams)
{
subs = df_total[df_total$Channel == i,]
g_temp = graph_from_data_frame(subs, vertices = users_df[,c("id", "Team", "index", "comp_team")])
write.graph(g_temp, paste("channel_nets/", i, ".graphml", sep = ""), format = "graphml")
E(g_temp)$weight = 1
g_simp_temp = igraph::simplify(g_temp, remove.multiple = TRUE, remove.loops = FALSE)
g_simp_temp = delete_vertices(g_simp_temp, v = V(g_simp_temp)[degree(g_simp_temp) == 0])
V(g_simp_temp)$Name = V(g_simp_temp)$comp_team
#print(col)
net_list[[j]] = g_simp_temp
j = j + 1
plotNetwork(g_simp_temp, main = i, coms = V(g_simp_temp)$index)
}
Density order networks
pdf('plotTeamNetworks_ordered.pdf')
par(mfrow=c(5,4), mar=c(0,0,2,0), cex.main=3)
ordered = order(sapply(net_list, edge_density))
for (i in ordered)
{
g = net_list[[i]]
plotNetwork(g, main = teams[i], coms = V(g)$index)
}
Other Channels
pdf('plotChannelNetworks.pdf')
par(mfrow=c(2,2), mar=c(0,0,2,0), cex.main=3)
net_list = list()
j = 1
for (i in channels_df$name)
{
if(!i %in% teams & nrow(df_total[df_total$Channel == i,]) > 0)
{
subs = df_total[df_total$Channel == i,]
g_temp = graph_from_data_frame(subs, vertices = users_df[,c("id", "Team", "index", "comp_team")])
write.graph(g_temp, paste("channel_nets/", i, ".graphml", sep = ""), format = "graphml")
E(g_temp)$weight = 1
g_simp_temp = igraph::simplify(g_temp, remove.multiple = TRUE, remove.loops = FALSE)
g_simp_temp = delete_vertices(g_simp_temp, v = V(g_simp_temp)[degree(g_simp_temp) == 0])
V(g_simp_temp)$Name = V(g_simp_temp)$comp_team
net_list[[j]] = g_simp_temp
j = j + 1
plotNetwork(g_simp_temp, main = i, coms = V(g_simp_temp)$index)
}
}
Density order networks
pdf('plotChannelNetworks_ordered.pdf')
par(mfrow=c(2,2), mar=c(0,0,2,0), cex.main=3)
ordered = order(sapply(net_list, edge_density))
for (i in ordered)
{
g = net_list[[i]]
plotNetwork(g, main = teams[i], coms = V(g)$index)
}
Interactions with Org. Team
df_org_int = data.frame()
for (i in unique(df_total$Channel))
{
subs = df_total[df_total$Channel == i,]
part = users_df$id[!grepl("Dropped Out", users_df$Team)]
n = nrow(subs[subs$To %in% users_df$id[users_df$Team %in% c("Mentor", "Organiser", "Project Partner")] & !subs$From %in% users_df$id[users_df$Team %in% c("Mentor", "Organiser", "Project Partner")],])
n1 = nrow(subs[!subs$To %in% users_df$id[users_df$Team %in% c("Mentor", "Organiser", "Project Partner")] & subs$From %in% users_df$id[users_df$Team %in% c("Mentor", "Organiser", "Project Partner")],])
df_org_int = rbind(df_org_int, data.frame(channel = i, messages_org = n+n1))
}
Plot
plt = ggplot(df_org_int[df_org_int$channel %in% teams,], aes(x = reorder(channel, messages_org), y = messages_org)) + geom_bar(stat = "identity") + theme_bw(base_size = 20) + xlab("Teams") + ylab("# Interactions with Org Team") + ggtitle("Interactions with Organising Team") + theme(axis.text.x = element_text(angle = 0,hjust=1,vjust=1)) + coord_flip()
ggplotly(plt)
ggsave('viz/interactions_org_team.pdf', width = 8, height = 6)
Camille’s Code
library("gtsummary")
country %>%
select(Gender, Age) %>%
tbl_summary(
statistic = list(all_continuous() ~ "{median} ({min}, {max})",
all_categorical() ~ "{n} ({p}%)"))
| Characteristic |
N = 48 |
| Gender |
|
| Female |
27 (56%) |
| Male |
21 (44%) |
| Age |
22 (15, 38) |
#ggsave('viz/gender_tbl_summary.pdf', width = 6, height = 5)
ggplot(country)+
aes(x = reorder(Nationality, Nationality, function(x) length(x))) +
geom_bar(stat="count",width = 0.9, position = position_dodge())+
scale_y_continuous(limits = c(0, 10), breaks = c(0,2,4,6,8,10))+
labs(y = "Count",x = "Nationality")+
theme_bw() + geom_text(stat = "count", aes(label = ""),
position = position_dodge(width = 0.9), size = 2.5, hjust = -0.5)+
coord_flip()
ggsave('viz/Nationality.pdf', width = 6, height = 5)

ggplot(country)+
aes(x = reorder(Country.of.Residence, Country.of.Residence, function(x) length(x))) +
geom_bar(stat="count",width = 0.9, position = position_dodge())+
scale_y_continuous(limits = c(0, 10), breaks = c(0,2,4,6,8,10))+
labs(y = "Count",x = "Country of origin")+
theme_bw() + geom_text(stat = "count", aes(label = ""),
position = position_dodge(width = 0.9), size = 2.5, hjust = -0.5)+
coord_flip()
ggsave('viz/Country_of_origin.pdf', width = 6, height = 5)

Ecolution has one more member than the plot in the report. That is because of one user who listed their own name as the team name. I matched the user with their corresponding team - Ecolution.
ggplot(country)+
aes(x = reorder(Team.Name, Team.Name, function(x) length(x))) +
geom_bar(stat="count",width = 0.9, position = position_dodge())+
scale_y_continuous(limits = c(0, 5), breaks = c(0,1,2,3,4, 5))+
labs(y = "Count",x = "Teams")+
theme_bw() + geom_text(stat = "count", aes(label = ""),
position = position_dodge(width = 0.9), size = 2.5, hjust = -0.5)+
coord_flip()
ggsave('viz/Team_size.pdf', width = 6, height = 5)

country %>%
ggplot( aes(x=Gender, y=Age, fill=Gender)) +
geom_boxplot()+
scale_fill_brewer()+
geom_jitter(color="black", size=0.4, alpha=0.9)+
scale_y_continuous(breaks = c(10, 15, 20, 25, 30, 35, 40, 45))+
labs(x = "", y="Age", fill = "Gender")+
theme_bw()
ggsave('viz/Gender_Age.pdf', width = 6, height = 5)

LS0tCnRpdGxlOiAiQ3Jvd2Q0U0RHIFNsYWNrIC0gQW5hbHlzaXMgZm9yIFJlcG9ydCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKQW5hbHlzaXMgb24gdGhlIENyb3dkNFNERyBTbGFjayBpbmNsdWRlZCBpbiB0aGUgUmVwb3J0CgoKYGBge3J9CgpsaWJyYXJ5KCJSQ29sb3JCcmV3ZXIiKSAjQ29sb3IgUGFsZXR0ZQpsaWJyYXJ5KCJ3b3JkY2xvdWQiKSAjV29yZGNsb3VkIExpYnJhcnkKbGlicmFyeSgianNvbmxpdGUiKSAjSGFuZGxpbmcgSlNPTiBGaWxlcwpsaWJyYXJ5KCJnZ3Bsb3QyIikgI1Bsb3RzCmxpYnJhcnkoImlncmFwaCIpICNOZXR3b3JrcyBhbmQgQW5hbHlzaXMKbGlicmFyeSgic2NhbGVzIikgI1Bsb3QgQWVzdGhldGljcwpsaWJyYXJ5KCJ2aXNOZXR3b3JrIikgI0QzIGJhc2VkIE5ldHdvcmsgUGxvdHMKbGlicmFyeSgicGxvdGx5IikgI1Bsb3RzCmxpYnJhcnkoImRwbHlyIikgI0RhdGFmcmFtZSBNYW5pcHVsYXRpb25zCmxpYnJhcnkoInFncmFwaCIpICNOZXR3b3JrIGFuYWx5aXMgYW5kIFBsb3RzCmxpYnJhcnkoIm5ldHdvcmtEMyIpICNTYW5rZXkKCnNvdXJjZSgiY3VzdG9tLlIiKSAjU29tZSBDdXN0b20gRnVuY3Rpb25zIChjb3VydGVzeSBNYXJjKQoKYGBgCgpgYGB7cn0KCnVzZXJzX2RmID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvdXNlcnNfYW4uY3N2IikgI1VzZXJzIE1ldGEKCmNoYW5uZWxzX2RmID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvY2hhbm5lbHMuY3N2IikgI0NoYW5uZWxzIE1ldGEKZGZfZW1vdGljb25fcmVzcG9uc2VzID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvZW1vdGljb25fcmVzcG9uc2VzLmNzdiIpWywtYygxKV0gI0Vtb3RpY29uIFJlc3BvbnNlcwpkZl90ZXh0X21lbnRpb25zID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvdGV4dF9tZW50aW9ucy5jc3YiKVssLWMoMSldICNUZXh0IE1lbnRpb25zCmRmX3RleHRzID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvdGV4dHNfY2hhbm5lbC5jc3YiKVssLWMoMSldICNNZXNzYWdlcwpkZl9jaGFubmVsX2pvaW4gPSByZWFkLmNzdigiZGF0YV9maWxlcy9jaGFubmVsX2pvaW4uY3N2IilbLC1jKDEpXSAjQ2hhbm5lbCBKb2luIE1lc3NhZ2VzCmRmX3RocmVhZHMgPSByZWFkLmNzdigiZGF0YV9maWxlcy90aHJlYWRzX2NoYW5uZWwuY3N2IilbLC1jKDEpXSAjVGhyZWFkcwoKZGZfdGhfcmVwID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvdGhyZWFkX3JlcGxpZXMuY3N2IilbLGMoLTEpXSAjVGhyZWFkIFJlcGxpZXMKCnRlYW1zID0gYygidGVhbS1hcXVhdGljcyIsICJ0ZWFtLWZsb29kZmluZGVyIiwgInRlYW0tdG9zZWV0b2NhcmUiLCAidGVhbS1zYWZld2F0ZXJmb3Job21lbGVzcyIsICJ0ZWFtLXdvdHRlciIsICJ0ZWFtLWNvbGxhYm9yYXRpdmV3YXRlcm1vbml0b3JpbmciLCAidGVhbWZsb29kbWFwcyIsICJ0ZWFtLWZpbHN0ZWluZXIiLCAidGVhbS1tbmwiLCAidGVhbS1yYWlud2F0ZXJoYXJ2ZXN0aW5nIiwgInRlYW0tZ2FydWRhc2F2aW9yIiwgInRlYW0tc2FmZXNreWUiLCAidGVhbS1mZXdzIiwgInRlYW0tcmFpbndhdGVyLWNvbGxlY3Rpb24iLCAidGVhbS1wb3RhbW9pIiwgInRlYW0tZGFpbHl3YXRlciIsICJ0ZWFtX2Vjb2x1dGlvbiIsICJ0ZWFtLXRob3VzYW5kLXdhdGVycyIsICJ3YXJib24tZm9vdHByaW50IikKCgoKYGBgCgpQbG90IEZ1bmN0aW9ucwoKYGBge3J9CgojVG9nZ2xlIGZvciBhcnJvd2hlYWRzIGlmIGRpcmVjdGVkCgp2aXNQbG90X25pID0gZnVuY3Rpb24oc3ViZ3JhcGgsIGNvbW11bml0aWVzID0gcmVwKDEsIGxlbmd0aChWKHN1YmdyYXBoKSkpLCBub2Rlc2l6ZSA9IDEwLCBlZGdld2lkdGggPSAwLCB0aXRsZT0iIiwgdGV4dHNpemUgPSBub2Rlc2l6ZSwgbGF5b3V0ID0gImxheW91dF9uaWNlbHkiLCBkaXJlY3RlZCA9IFRSVUUpCnsKICBub2RlcyA8LSBkYXRhLmZyYW1lKGlkID0gVihzdWJncmFwaCkkbmFtZSwgbmFtZSA9IFYoc3ViZ3JhcGgpJHByaW50LCBncm91cCA9IGNvbW11bml0aWVzKQogIG5vZGVzJGZvbnQuc2l6ZTwtdGV4dHNpemUgKyAyMAogIG5vZGVzJGZvbnQuY29sb3IgPSAiZ3JleSIKICBub2RlcyRmb250LmZhY2UgPSAiY2FsaWJyaSIKICBub2RlcyRsYWJlbCA9IG5vZGVzJG5hbWUKICBub2RlcyR2YWx1ZSA9IG5vZGVzaXplCiAgZWRnZXMgPC0gZGF0YS5mcmFtZShnZXQuZWRnZWxpc3Qoc3ViZ3JhcGgpKQogIGVkZ2VzJHdpZHRoID0gZWRnZXdpZHRoCiAgY29sbmFtZXMoZWRnZXMpPC1jKCJmcm9tIiwidG8iLCJ3aWR0aCIpCiAgcGx0ID0gdmlzTmV0d29yayhub2RlcywgZWRnZXMsIGhlaWdodCA9ICIxMDAwcHgiLCB3aWR0aCA9ICIxMDAlIixtYWluID0gdGl0bGUpJT4lCiAgICB2aXNJZ3JhcGhMYXlvdXQobGF5b3V0ID0gbGF5b3V0KSAlPiUKICAgIHZpc1BoeXNpY3Moc29sdmVyID0gInJlcHVsc2lvbiIsIGhpZXJhcmNoaWNhbFJlcHVsc2lvbiA9ICJub2RlRGlzdGFuY2UiKSAlPiUKICAgIHZpc09wdGlvbnMoaGlnaGxpZ2h0TmVhcmVzdCA9IFRSVUUpICU+JQogICAgdmlzTm9kZXMoc2NhbGluZyA9IGxpc3QobWluID0gMTAsIG1heCA9IDMwKSkgJT4lCiAgICB2aXNFZGdlcyhzbW9vdGggPSBULCBjb2xvciA9IGxpc3QoY29sb3IgPSAibGlnaHRibHVlIiwgaGlnaGxpZ2h0ID0gImJsdWUiKSkgJT4lIAogICAgdmlzR3JvdXBzKGdyb3VwbmFtZSA9ICIyIiwgY29sb3IgPSBsaXN0KGJhY2tncm91bmQgPSAibGlnaHRncmF5Iixib3JkZXIgPSAiYmxhY2siKSkgJT4lCiAgICB2aXNJbnRlcmFjdGlvbihrZXlib2FyZCA9IFQsCiAgICAgICAgICAgICAgICAgICBkcmFnTm9kZXMgPSBULCAKICAgICAgICAgICAgICAgICAgIGRyYWdWaWV3ID0gVCwgCiAgICAgICAgICAgICAgICAgICB6b29tVmlldyA9IFQpCiAgCiAgaWYgKGRpcmVjdGVkID09IFRSVUUpCiAgICBwbHQgPSBwbHQgJT4lIHZpc0VkZ2VzKGFycm93cyA9InRvIikKICAKICByZXR1cm4ocGx0KQp9CgojUGxvdCBGdW5jdGlvbiB3aXRoIHFncmFwaCBsaWJyYXJ5CnBsb3ROZXR3b3JrIDwtIGZ1bmN0aW9uKEdzdWIsIGxheW91dD0nZGVmYXVsdCcsbWFpbmU9JycsIGNvbXM9TlVMTCwgdGVhbXMgPSBOVUxMKXsKICAKICAKICBpZiAoaXMubnVsbChjb21zKSl7CiAgICBjb20gPC0gY2x1c3Rlcl93YWxrdHJhcChHc3ViKQogICAgVihHc3ViKSRjb2xvciA8LSBjb20kbWVtYmVyc2hpcCsxCiAgfSBlbHNlIHsKICAgICNjb2wgPSBtZXJnZShkYXRhLmZyYW1lKHRlYW0gPSBjb21zKSwgbGlzdCkKICAgIFYoR3N1YikkY29sb3IgPC0gY29tcwogIH0KICB2ZXJ0ZXguZnJhbWUuY29sb3IgPC0gVihHc3ViKSRjb2xvcgogIAogIHNpemUgPC0gYWJzKHN0cmVuZ3RoKEdzdWIsIGxvb3BzPVQpKQogIGlmIChtYXgoc2l6ZSwgbmEucm09VCk+MCl7IAogICAgc2l6ZSA8LSAzMCAqIHNpemUgLyBtYXgoc2l6ZSwgbmEucm09VCkKICB9IGVsc2UgewogICAgc2l6ZSA8LSAxCiAgfQogIAogIGlmIChpcy5udWxsKEUoR3N1Yikkd2VpZ2h0KSl7CiAgICB3aWR0aCA8LSAxCiAgICBlZGdlLmNvbG9yIDwtIE5BIAogICAgbCA8LSBsYXlvdXRfd2l0aF9mcgogICAgCiAgfSBlbHNlewogICAgd2VpZ2h0IDwtIGFicyhFKEdzdWIpJHdlaWdodCkKICAgIHdpZHRoIDwtIC4wMSArIDUgKiB3ZWlnaHQgLyBtYXgod2VpZ2h0LCBuYS5ybT1UKQogICAgZWRnZS5jb2xvciA8LSBpZmVsc2UoRShHc3ViKSR3ZWlnaHQgPiAwLCAnZ3JheScsICdibHVlJykKICAgIGUgPC0gZ2V0LmVkZ2VsaXN0KEdzdWIsIG5hbWVzPUYpCiAgICBlIDwtIGNiaW5kKGUsIEUoR3N1Yikkd2VpZ2h0KQogICAgbCA8LSBxZ3JhcGgubGF5b3V0LmZydWNodGVybWFucmVpbmdvbGQoZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZjb3VudD12Y291bnQoR3N1YiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmVhPTEqKHZjb3VudChHc3ViKV4yKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcHVsc2UucmFkPTUqKHZjb3VudChHc3ViKV4yKSkKICAgIAogIH0KICAKICBpZiAobGF5b3V0IT0nZGVmYXVsdCcpewogICAgbCA8LSBsYXlvdXQKICB9CiAgCiAgcGxvdChHc3ViLAogICAgICAgbGF5b3V0PWwsCiAgICAgICBlZGdlLndpZHRoID0gd2lkdGgsCiAgICAgICBlZGdlLmNvbG9yPWVkZ2UuY29sb3IsCiAgICAgICB2ZXJ0ZXgubGFiZWwgPSBWKEdzdWIpJE5hbWUsCiAgICAgICB2ZXJ0ZXgubGFiZWwuY29sb3IgPSAiYmxhY2siLAogICAgICAgdmVydGV4LmxhYmVsLmNleCA9IDAuNywKICAgICAgIHZlcnRleC5zaXplID0gMyAqIHNxcnQoc2l6ZSksCiAgICAgICBlZGdlLmN1cnZlZD0wLjEsCiAgICAgICB2ZXJ0ZXguZnJhbWUuY29sb3IgPSB2ZXJ0ZXguZnJhbWUuY29sb3IsIAogICAgICAgZWRnZS5hcnJvdy53aWR0aCA9IDAuMSwKICAgICAgIGVkZ2UuYXJyb3cuc2l6ZSA9IDAuMSwKIyAgICAgICBtYWluPW1haW5lLAojICAgICAgIG1haW4uY2V4ID0gMC4zLAogICAgICAgY29sPSdncmF5JwopCiAgdGl0bGUobWFpbiA9IG1haW5lICwgY2V4Lm1haW4gPSAwLjcpCiAgcmV0dXJuKGwpCn0KCmBgYAoKIyBTb3J0IEFjdGl2aXR5IGJ5IFdlZWsKCmBgYHtyfQoKU3lzLnNldGVudihUWj0iR01UIikKZGZfdGV4dHMkc291cmNlX21lc3NhZ2VfdGltZXN0YW1wID0gYXMuUE9TSVhjdChkZl90ZXh0cyRzb3VyY2VfbWVzc2FnZV90aW1lc3RhbXAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIikKCmRmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5ID0gZGF0YS5mcmFtZSgpCgpmb3IgKGkgaW4gdW5pcXVlKGRmX3RleHRzJGNoYW5uZWwpKQp7CiAgaWYgKFQpCiAgewogICAgb2JqID0gaGlzdChkZl90ZXh0cyRzb3VyY2VfbWVzc2FnZV90aW1lc3RhbXBbZGZfdGV4dHMkY2hhbm5lbCA9PSBpXSwgYnJlYWtzID0gIndlZWtzIiwgcGxvdCA9IEYpCiAgICBmb3IgKGogaW4gMTpsZW5ndGgob2JqJGNvdW50cykpCiAgICB7CiAgICAgIGRmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5ID0gcmJpbmQoZGZfY2hhbm5lbF9hY3Rpdml0eV93ZWVrbHksIGRhdGEuZnJhbWUoY2hhbm5lbCA9IGksIGFjdGl2aXR5ID0gb2JqJGNvdW50c1tqXSwgd2Vla3MgPSBvYmokYnJlYWtzW2pdKSkKICAgIH0KICB9CiAgCn0KYGBgCgojIFdvcmRjbG91ZHMKCmBgYHtyLGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTR9CgpsaXN0ID0gc29ydCh1bmlxdWUoZGZfY2hhbm5lbF9hY3Rpdml0eV93ZWVrbHkkd2Vla3MpKQp3ZWVrcyA9IGxpc3QKd2MgPSBsaXN0KCkKCmRpci5jcmVhdGUoJ3ZpeicsIHNob3dXYXJuaW5ncyA9IEYpCgoKZm9yIChpIGluIGxpc3RbMToobGVuZ3RoKGxpc3QpKV0pCnsKICBzZXQuc2VlZCgxMjM0KQogICMgcHJpbnQoYXMuUE9TSVhjdChpLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpKQogIHBkZihwYXN0ZTAoJ3Zpei93b3JkY2xvdWRzX3dlZWsnLG1hdGNoKGksbGlzdCksJy5wZGYnKSwgMTAsIDEwKQogICMgcGFyKG1mcm93PWMoMywzKSkKICAgd2MgPSB3b3JkY2xvdWQod29yZHMgPSBkZl9jaGFubmVsX2FjdGl2aXR5X3dlZWtseSRjaGFubmVsW2RmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5JHdlZWtzID09IGldLCAKICAgICAgICAgICAgICAgICAgZnJlcSA9IGRmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5JGFjdGl2aXR5W2RmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5JHdlZWtzID09IGldLCAKICAgICAgICAgICAgICAgICAgbWluLmZyZXEgPSAwLG1heC53b3Jkcz0xMDAsIHJhbmRvbS5vcmRlcj1GQUxTRSwgcm90LnBlcj0wLCBjb2xvcnM9YnJld2VyLnBhbCg4LCAiRGFyazIiKSkgCiAgIGRldi5vZmYoKQp9CgpgYGAKCgojIFRpbWV6b25lcyAKCmBgYHtyLCBnaWcud2lkdGg9MywgZmlnLmhlaWdodD0yfQpwbHQgPSBnZ3Bsb3QodXNlcnNfZGYsIGFlcyh4ID0gcmVvcmRlcih0aW1lem9uZSx0aW1lem9uZSwKICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeCktbGVuZ3RoKHgpKSkpICsgCiAgZ2VvbV9iYXIoc3RhdCA9ICJjb3VudCIpICsgCiAgdGhlbWVfYncoKSArIAogICMgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCxoanVzdD0wLjk1LHZqdXN0PTAuMikpICsgCiAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSxoanVzdD0xLHZqdXN0PTEpKSArIAogIHlsYWIoIk51bWJlciBvZiBVc2VycyIpICsgCiAgIyBzY2FsZV95X2xvZzEwKCkgKwogIGdndGl0bGUoIlVzZXJzIFRpbWV6b25lcyIgKSAgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgCiAgeGxhYigiVGltZXpvbmVzIikKZ2dwbG90bHkocGx0KQpgYGAKCgpgYGB7ciwgZ2lnLndpZHRoPTMsIGZpZy5oZWlnaHQ9Mn0KZ2dzYXZlKCd2aXovdGltZXpvbmVzLnBkZicsIHdpZHRoID0gNywgaGVpZ2h0ID0gNCkKYGBgCgoKIyBDaGFubmVsIGFjdGl2aXR5CgpgYGB7cn0KCgojIHRvdCA9IGFnZ3JlZ2F0ZShkZl9jaGFubmVsX2FjdGl2aXR5X21vbnRobHkkYWN0aXZpdHksIGJ5ID0gbGlzdChjaGFubmVsID1kZl9jaGFubmVsX2FjdGl2aXR5X21vbnRobHkkY2hhbm5lbCksIEZVTiA9IHN1bSkKCnRiID0gdGFibGUoZGZfdGV4dHMkY2hhbm5lbCkKdG90ID0gZGF0YS5mcmFtZSgnY2hhbm5lbCc9bmFtZXModGIpLCB4ID0gYXMubnVtZXJpYyh0YikpCiMgdG90JHggPSB0b3QkeC9zdW0odG90JHgpCgogcGx0ID0gZ2dwbG90KHRvdCwgYWVzKHg9cmVvcmRlcihjaGFubmVsLHgpLCB5ID0geCkpICsgCiAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE1KSArIAogICAjIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGFuZ2xlID0gOTApKSArIAogICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsaGp1c3Q9MSx2anVzdD0xKSkgKyAKICAgeGxhYigiIikgKyB5bGFiKCJOdW1iZXIgb2YgUG9zdHMiKSArIGdndGl0bGUoIlRvdGFsIG51bWJlciBvZiBwb3N0cyIgKSAgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgY29vcmRfZmxpcCgpIAogIysgc2NhbGVfeV9sb2cxMCgpCiAKZ2dwbG90bHkocGx0KQpgYGAKCgoKYGBge3IsIGdpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTJ9Cmdnc2F2ZSgndml6L2NoYW5uZWxzLnBkZicsIHdpZHRoID0gNywgaGVpZ2h0ID0gOCkKYGBgCgoKQ291bnRyeSBvZiBPcmlnaW4KCgpTYW5rZXkgTG9jYXRpb24KCmBgYHtyfQoKY291bnRyeSA9IHJlYWQuY3N2KCJkYXRhX2ZpbGVzL3BhcnRpY2lwYW50cy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgpub2RlcyA9IGRhdGEuZnJhbWUoTmFtZSA9IHVuaW9uKGNvdW50cnkkTmF0aW9uYWxpdHksIGNvdW50cnkkVGVhbS5OYW1lKSkKCmxpbmtzID0gY291bnRyeVssYygiVGVhbS5OYW1lIiwgIk5hdGlvbmFsaXR5IildICU+JSBncm91cF9ieShUZWFtLk5hbWUsIE5hdGlvbmFsaXR5KSAlPiUgc3VtbWFyaXNlKGNvdW50ID0gbigpKQpsaW5rcyRJRHNvdXJjZSA8LSBtYXRjaChsaW5rcyRUZWFtLk5hbWUsIG5vZGVzJE5hbWUpLTEgCmxpbmtzJElEdGFyZ2V0IDwtIG1hdGNoKGxpbmtzJE5hdGlvbmFsaXR5LCBub2RlcyROYW1lKS0xCgpwbHQgPSBzYW5rZXlOZXR3b3JrKExpbmtzID0gbGlua3MsIE5vZGVzID0gbm9kZXMsCiAgICAgICAgICAgICAgICAgICAgIFNvdXJjZSA9ICJJRHNvdXJjZSIsIFRhcmdldCA9ICJJRHRhcmdldCIsCiAgICAgICAgICAgICAgICAgICAgIFZhbHVlID0gImNvdW50IiwgTm9kZUlEID0gIk5hbWUiLCAKICAgICAgICAgICAgICAgICAgICAgc2lua3NSaWdodD1GQUxTRSwgZm9udFNpemUgPSAxMykKcGx0Cgp2aXNTYXZlKHBsdCwgJ3Zpei9zYW5rZXlfbmF0aW9uYWxpdGllcy5odG1sJykKCiNnZ3NhdmUoJ3Zpei9zYW5rZXlfbmF0aW9uYWxpdHkucGRmJywgaGVpZ2h0PTUsIHdpZHRoPTEwKQpgYGAKClNhbmtleSBUaW1lem9uZQoKYGBge3J9Cm9yZ190ZWFtX3BhcnQgPSB1c2Vyc19kZlshdXNlcnNfZGYkVGVhbSAlaW4lIGMoIk9yZ2FuaXNlciIsICJNZW50b3IiLCAiUHJvamVjdCBQYXJ0bmVyIiwgIk5PIElERUEiLCAiRXh0ZXJuYWwgUmVzb3VyY2UiKSxdCm9yZ190ZWFtX3BhcnQgPSBvcmdfdGVhbV9wYXJ0WyFncmVwbCgiRHJvcHBlZCBPdXQiLCBvcmdfdGVhbV9wYXJ0JFRlYW0pLF0KCiNvcmdfdGVhbV9wYXJ0JFRlYW0gPSBhcy5jaGFyYWN0ZXIob3JnX3RlYW1fcGFydCRUZWFtKQoKbm9kZXMgPSBkYXRhLmZyYW1lKE5hbWUgPSB1bmlvbihvcmdfdGVhbV9wYXJ0JFRlYW0sIG9yZ190ZWFtX3BhcnQkdGltZXpvbmUpKQpub2RlcyROYW1lID0gYXMuZmFjdG9yKG5vZGVzJE5hbWUpCgoKbGlua3MgPSBvcmdfdGVhbV9wYXJ0WyxjKCJUZWFtIiwgInRpbWV6b25lIildICU+JSBncm91cF9ieShUZWFtLCB0aW1lem9uZSkgJT4lIHN1bW1hcmlzZShjb3VudCA9IG4oKSkKbGlua3MkSURzb3VyY2UgPC0gbWF0Y2gobGlua3MkVGVhbSwgbm9kZXMkTmFtZSktMSAKbGlua3MkSUR0YXJnZXQgPC0gbWF0Y2gobGlua3MkdGltZXpvbmUsIG5vZGVzJE5hbWUpLTEKCnBsdCA9IHNhbmtleU5ldHdvcmsoTGlua3MgPSBsaW5rcywgTm9kZXMgPSBub2RlcywKICAgICAgICAgICAgICAgICAgICAgU291cmNlID0gIklEc291cmNlIiwgVGFyZ2V0ID0gIklEdGFyZ2V0IiwKICAgICAgICAgICAgICAgICAgICAgVmFsdWUgPSAiY291bnQiLCBOb2RlSUQgPSAiTmFtZSIsIAogICAgICAgICAgICAgICAgICAgICBzaW5rc1JpZ2h0PUZBTFNFLCBmb250U2l6ZSA9IDEzKQpwbHQKCnZpc1NhdmUocGx0LCAndml6L3NhbmtleV90aW1lem9uZXMuaHRtbCcpCgojZ2dzYXZlKCd2aXovc2Fua2V5X3RpbWV6b25lcy5wZGYnLCBoZWlnaHQ9NSwgd2lkdGg9MTApCgpgYGAKCgoKTm8uIG9mIFBvc3RzICsgSGVhdG1hcCAoQ3V0IGFmdGVyIHdlZWsgNikKCiMgQ2hhbm5lbCBkeW5hbWljcyAoSGVhdG1hcCBvZiBBY3Rpdml0eSkKCmBgYHtyfQoKc3RvcmUgPSBmdW5jdGlvbihhKQp7CiAgaWYgKGlzLmludGVnZXIoYSkgJiBsZW5ndGgoYSk9PTApCiAgICByZXR1cm4oMCkKICBlbHNlCiAgICByZXR1cm4oYSkKfQoKd2Vla3MgPSBzb3J0KHVuaXF1ZShkZl9jaGFubmVsX2FjdGl2aXR5X3dlZWtseSR3ZWVrcykpCmNoYW5uZWxzID0gdW5pcXVlKGRmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5JGNoYW5uZWwpCgpjaGFubmVsX2FjdF9tYXQgPSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChjaGFubmVscyksIG5jb2wgPSBsZW5ndGgod2Vla3MpKQoKCmZvciAoaSBpbiAxOmxlbmd0aCh3ZWVrcykpCnsKICBmb3IgKGogaW4gMTpsZW5ndGgoY2hhbm5lbHMpKQogIHsKICAgIGNoYW5uZWxfYWN0X21hdFtqLGldID0gc3RvcmUoZGZfY2hhbm5lbF9hY3Rpdml0eV93ZWVrbHkkYWN0aXZpdHlbZGZfY2hhbm5lbF9hY3Rpdml0eV93ZWVrbHkkY2hhbm5lbCAlaW4lIGNoYW5uZWxzW2pdICYgZGZfY2hhbm5lbF9hY3Rpdml0eV93ZWVrbHkkd2Vla3MgPT0gd2Vla3NbaV1dKQogIH0KICAKfQoKY29sbmFtZXMoY2hhbm5lbF9hY3RfbWF0KSA9IGFzLlBPU0lYY3Qod2Vla3MsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIikKcm93bmFtZXMoY2hhbm5lbF9hY3RfbWF0KSA9IGNoYW5uZWxzCgoKYGBgCgoKCmBgYHtyfQpkYXRhIDwtIGRmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5Cm1vbnRocyA8LSBzb3J0KHVuaXF1ZShkYXRhJHdlZWtzKSkKY2hhbm5lbHMgPC0gdW5pcXVlKGRhdGEkY2hhbm5lbCkKCk0gPC0gbWF0cml4KE5BLGxlbmd0aChjaGFubmVscyksIGxlbmd0aCh3ZWVrcykpCgpmb3IgKGkgaW4gMTpucm93KGRhdGEpKXsKICBNW21hdGNoKGRhdGEkY2hhbm5lbFtpXSwgY2hhbm5lbHMpLCBtYXRjaChkYXRhJHdlZWtzW2ldLCB3ZWVrcyldIDwtIGRhdGEkYWN0aXZpdHlbaV0KfQoKcm93bmFtZXMoTSkgPC0gY2hhbm5lbHMKY29sbmFtZXMoTSkgPC0gcGFzdGUoJ1dlZWsnLDE6bmNvbChNKSkKCmBgYAoKCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD00fQoKcGRmKCd2aXovY2hhbm5lbHNfaGVhdG1hcC5wZGYnLCA3LDEwKQoKcGVhayA8LSBhcHBseShNLDEsIHdoaWNoLm1heCkKTTEgPC0gTSAvIGFwcGx5KE0sMSxtYXgsIG5hLnJtPVQpCgpNMVtpcy5uYShNMSldIDwtIDAKaGVhdG1hcC4wKE0xW29yZGVyKHBlYWspLDE6Nl0sIGNleFJvdyA9IDEsIGNleENvbCA9IDIsIGNvbD1jb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwiZGFya3JlZCIpKSwgbWFyPWMoMTAsMTUpKQoKZGV2Lm9mZigpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGg9MywgZmlnLmFzcD0xfQoKdG90X3Bvc3RzIDwtIGFwcGx5KE0sMixzdW0sIG5hLnJtPVQpCgpwZGYoJ3Zpei9hY3Rpdml0eV90b3RhbC5wZGYnLCA2LDYpCnBsb3QuMCh0b3RfcG9zdHMsIHR5cGU9J28nLCAKICAgICAgIHlsYWI9J051bWJlciBvZiBwb3N0cycsIHhsYWI9J1dlZWsnLCBsd2Q9MikKZGV2Lm9mZigpCmBgYAoKQWdncmVnYXRlZCBNZW50aW9ucyBhbmQgUmVhY3Rpb24gTmV0d29ya3MKCmBgYHtyfQojT3ZlcmFsbCAtIG1lbnRpb25zCgp0ZXh0X21lbnQxID0gbWVyZ2UoZGZfdGV4dF9tZW50aW9ucywgdXNlcnNfZGZbLGMoImlkIiwgIlRlYW0iKV0sIGJ5LnggPSAiZnJvbSIsIGJ5LnkgPSAiaWQiKQpjb2xuYW1lcyh0ZXh0X21lbnQxKVtjb2xuYW1lcyh0ZXh0X21lbnQxKSA9PSAiVGVhbSJdID0gIkZyb21fVGVhbSIKCnRleHRfbWVudDEgPSBtZXJnZSh0ZXh0X21lbnQxLCB1c2Vyc19kZlssYygiaWQiLCAiVGVhbSIpXSwgYnkueCA9ICJ0byIsIGJ5LnkgPSAiaWQiKQpjb2xuYW1lcyh0ZXh0X21lbnQxKVtjb2xuYW1lcyh0ZXh0X21lbnQxKSA9PSAiVGVhbSJdID0gIlRvX1RlYW0iCgpnX2dyb3VwZWRfbWVudGlvbnMgPSBncmFwaF9mcm9tX2RhdGFfZnJhbWUodGV4dF9tZW50MVssYygiRnJvbV9UZWFtIiwgIlRvX1RlYW0iLCAidGltZXN0YW1wIiwgImNoYW5uZWwiLCAiZnJvbSIsICJ0byIpXSwgZGlyZWN0ZWQgPSBUUlVFKQoKRShnX2dyb3VwZWRfbWVudGlvbnMpJHdlaWdodCA9IDEKCmdfZ3JvdXBlZF9tZW50aW9ucyA9IGlncmFwaDo6c2ltcGxpZnkoZ19ncm91cGVkX21lbnRpb25zLCByZW1vdmUubG9vcHMgPSBGQUxTRSwgcmVtb3ZlLm11bHRpcGxlID0gVFJVRSkKClYoZ19ncm91cGVkX21lbnRpb25zKSRwcmludCA9IFYoZ19ncm91cGVkX21lbnRpb25zKSRuYW1lCgpwbHQgPSB2aXNQbG90X25pKGdfZ3JvdXBlZF9tZW50aW9ucywgZWRnZXdpZHRoID0gcmVzY2FsZShFKGdfZ3JvdXBlZF9tZW50aW9ucykkd2VpZ2h0LCB0byA9IGMoMSwxMCkpLCBub2Rlc2l6ZSA9IGRlZ3JlZShnX2dyb3VwZWRfbWVudGlvbnMsIG1vZGUgPSAiaW4iKSkKCndyaXRlLmdyYXBoKGdfZ3JvdXBlZF9tZW50aW9ucywgIm5ldHdvcmtzL2dyb3VwZWRfbWVudGlvbnMuZ3JhcGhtbCIsIGZvcm1hdCA9ICJncmFwaG1sIikKCnBsdAoKdmlzU2F2ZShwbHQsICduZXR3b3Jrcy9uZXR3b3JrX2dyb3VwZWRfbWVudGlvbi5odG1sJykKYGBgCgpgYGB7cn0KI092ZXJhbGwgLSByZWFjdGlvbnMKCnJlYWNfbWVudDEgPSBtZXJnZShkZl9lbW90aWNvbl9yZXNwb25zZXMsIHVzZXJzX2RmWyxjKCJpZCIsICJUZWFtIildLCBieS54ID0gInJlYWN0aW9uX2J5IiwgYnkueSA9ICJpZCIpCmNvbG5hbWVzKHJlYWNfbWVudDEpW2NvbG5hbWVzKHJlYWNfbWVudDEpID09ICJUZWFtIl0gPSAiRnJvbV9UZWFtIgoKcmVhY19tZW50MSA9IG1lcmdlKHJlYWNfbWVudDEsIHVzZXJzX2RmWyxjKCJpZCIsICJUZWFtIildLCBieS54ID0gInNvdXJjZV9tZXNzYWdlX2Zyb20iLCBieS55ID0gImlkIikKY29sbmFtZXMocmVhY19tZW50MSlbY29sbmFtZXMocmVhY19tZW50MSkgPT0gIlRlYW0iXSA9ICJUb19UZWFtIgoKZ19ncm91cGVkX3JlYWN0aW9ucyA9IGdyYXBoX2Zyb21fZGF0YV9mcmFtZShyZWFjX21lbnQxWyxjKCJGcm9tX1RlYW0iLCAiVG9fVGVhbSIsICJzb3VyY2VfbWVzc2FnZV90aW1lc3RhbXAiLCAiY2hhbm5lbCIsICJzb3VyY2VfbWVzc2FnZV9mcm9tIiwgInJlYWN0aW9uX2J5IildLCBkaXJlY3RlZCA9IFRSVUUpCgpFKGdfZ3JvdXBlZF9yZWFjdGlvbnMpJHdlaWdodCA9IDEKCmdfZ3JvdXBlZF9yZWFjdGlvbnMgPSBpZ3JhcGg6OnNpbXBsaWZ5KGdfZ3JvdXBlZF9yZWFjdGlvbnMsIHJlbW92ZS5sb29wcyA9IEZBTFNFLCByZW1vdmUubXVsdGlwbGUgPSBUUlVFKQoKVihnX2dyb3VwZWRfcmVhY3Rpb25zKSRwcmludCA9IFYoZ19ncm91cGVkX3JlYWN0aW9ucykkbmFtZQoKcGx0ID0gdmlzUGxvdF9uaShnX2dyb3VwZWRfcmVhY3Rpb25zLCBlZGdld2lkdGggPSByZXNjYWxlKEUoZ19ncm91cGVkX3JlYWN0aW9ucykkd2VpZ2h0LCB0byA9IGMoMSwxMCkpLCBub2Rlc2l6ZSA9IGRlZ3JlZShnX2dyb3VwZWRfcmVhY3Rpb25zLCBtb2RlID0gImluIikpCgp3cml0ZS5ncmFwaChnX2dyb3VwZWRfcmVhY3Rpb25zLCAibmV0d29ya3MvZ3JvdXBlZF9yZWFjdGlvbnMuZ3JhcGhtbCIsIGZvcm1hdCA9ICJncmFwaG1sIikKCnBsdAoKdmlzU2F2ZShwbHQsICduZXR3b3Jrcy9uZXR3b3JrX2dyb3VwZWRfcmVhY3Rpb25zLmh0bWwnKQoKYGBgCgoKQ2hhbm5lbCB3aXNlIGludGVyYWN0aW9uIG5ldHdvcmtzCgpgYGB7cn0KCmxpYnJhcnkoUkNvbG9yQnJld2VyKQoKI3VzaW5nIGFsbCBpbnRlcmFjdGlvbnMKCnBkZigncGxvdFRlYW1OZXR3b3Jrcy5wZGYnKQpwYXIobWZyb3c9Yyg1LDQpLCBtYXI9YygwLDAsMiwwKSwgY2V4Lm1haW49MykKCmRmX2VtbyA9IGRmX2Vtb3RpY29uX3Jlc3BvbnNlc1ssYygxLDIsMyw1KV0KY29sbmFtZXMoZGZfZW1vKSA9IGMoIkZyb20iLCAiVG8iLCAiVGltZXN0YW1wIiwgIkNoYW5uZWwiKQpkZl9lbW8kVHlwZSA9ICJlbW90aWNvbl9yZXNwb25zZXMiCgoKZGZfdG10ID0gZGZfdGV4dF9tZW50aW9uc1shZGZfdGV4dF9tZW50aW9ucyR0byAlaW4lIGMoImNoYW5uZWwiLCAiaGVyZSIsICJldmVyeW9uZSIpLF0KY29sbmFtZXMoZGZfdG10KSA9IGMoIkZyb20iLCAiVG8iLCAiVGltZXN0YW1wIiwgIkNoYW5uZWwiKQpkZl90bXQkVHlwZSA9ICJ0ZXh0X21lbnRpb25zIgoKZGZfdG90YWwgPSByYmluZChkZl90bXQsIGRmX2VtbykKCgpuZXRfbGlzdCA9IGxpc3QoKQpqID0gMQoKI2NoYW5uZWxzID0gdW5pcXVlKGRmX3RvdGFsJENoYW5uZWwpIAoKZm9yIChpIGluIHRlYW1zKQp7CiAgCiAgICBzdWJzID0gZGZfdG90YWxbZGZfdG90YWwkQ2hhbm5lbCA9PSBpLF0KICAKICAgIGdfdGVtcCA9IGdyYXBoX2Zyb21fZGF0YV9mcmFtZShzdWJzLCB2ZXJ0aWNlcyA9IHVzZXJzX2RmWyxjKCJpZCIsICJUZWFtIiwgImluZGV4IiwgImNvbXBfdGVhbSIpXSkKICAKICAgIHdyaXRlLmdyYXBoKGdfdGVtcCwgcGFzdGUoImNoYW5uZWxfbmV0cy8iLCBpLCAiLmdyYXBobWwiLCBzZXAgPSAiIiksIGZvcm1hdCA9ICJncmFwaG1sIikKICAKICAgIEUoZ190ZW1wKSR3ZWlnaHQgPSAxCiAgICBnX3NpbXBfdGVtcCA9IGlncmFwaDo6c2ltcGxpZnkoZ190ZW1wLCByZW1vdmUubXVsdGlwbGUgPSBUUlVFLCByZW1vdmUubG9vcHMgPSAgRkFMU0UpCiAgCiAgICBnX3NpbXBfdGVtcCA9IGRlbGV0ZV92ZXJ0aWNlcyhnX3NpbXBfdGVtcCwgdiA9IFYoZ19zaW1wX3RlbXApW2RlZ3JlZShnX3NpbXBfdGVtcCkgPT0gMF0pCiAgICAKICAgIFYoZ19zaW1wX3RlbXApJE5hbWUgPSBWKGdfc2ltcF90ZW1wKSRjb21wX3RlYW0KICAKICAgICNwcmludChjb2wpCiAgCiAgICBuZXRfbGlzdFtbal1dID0gZ19zaW1wX3RlbXAKICAgIAogICAgaiA9IGogKyAxCiAgICAKICAgIHBsb3ROZXR3b3JrKGdfc2ltcF90ZW1wLCBtYWluID0gaSwgY29tcyA9IFYoZ19zaW1wX3RlbXApJGluZGV4KQogIAp9CgpgYGAKCkRlbnNpdHkgb3JkZXIgbmV0d29ya3MKCmBgYHtyfQoKCnBkZigncGxvdFRlYW1OZXR3b3Jrc19vcmRlcmVkLnBkZicpCnBhcihtZnJvdz1jKDUsNCksIG1hcj1jKDAsMCwyLDApLCBjZXgubWFpbj0zKQoKb3JkZXJlZCA9IG9yZGVyKHNhcHBseShuZXRfbGlzdCwgZWRnZV9kZW5zaXR5KSkKCmZvciAoaSBpbiBvcmRlcmVkKQp7CiAgZyA9IG5ldF9saXN0W1tpXV0KICAKICBwbG90TmV0d29yayhnLCBtYWluID0gdGVhbXNbaV0sIGNvbXMgPSBWKGcpJGluZGV4KQogIAp9CgpgYGAKCk90aGVyIENoYW5uZWxzCgpgYGB7cn0KCnBkZigncGxvdENoYW5uZWxOZXR3b3Jrcy5wZGYnKQpwYXIobWZyb3c9YygyLDIpLCBtYXI9YygwLDAsMiwwKSwgY2V4Lm1haW49MykKCm5ldF9saXN0ID0gbGlzdCgpCmogPSAxCgpmb3IgKGkgaW4gY2hhbm5lbHNfZGYkbmFtZSkKewogIGlmKCFpICVpbiUgdGVhbXMgJiBucm93KGRmX3RvdGFsW2RmX3RvdGFsJENoYW5uZWwgPT0gaSxdKSA+IDApCiAgewogICAgc3VicyA9IGRmX3RvdGFsW2RmX3RvdGFsJENoYW5uZWwgPT0gaSxdCiAgCiAgICBnX3RlbXAgPSBncmFwaF9mcm9tX2RhdGFfZnJhbWUoc3VicywgdmVydGljZXMgPSB1c2Vyc19kZlssYygiaWQiLCAiVGVhbSIsICJpbmRleCIsICJjb21wX3RlYW0iKV0pCiAgCiAgICB3cml0ZS5ncmFwaChnX3RlbXAsIHBhc3RlKCJjaGFubmVsX25ldHMvIiwgaSwgIi5ncmFwaG1sIiwgc2VwID0gIiIpLCBmb3JtYXQgPSAiZ3JhcGhtbCIpCiAgCiAgICBFKGdfdGVtcCkkd2VpZ2h0ID0gMQogICAgZ19zaW1wX3RlbXAgPSBpZ3JhcGg6OnNpbXBsaWZ5KGdfdGVtcCwgcmVtb3ZlLm11bHRpcGxlID0gVFJVRSwgcmVtb3ZlLmxvb3BzID0gIEZBTFNFKQogIAogICAgZ19zaW1wX3RlbXAgPSBkZWxldGVfdmVydGljZXMoZ19zaW1wX3RlbXAsIHYgPSBWKGdfc2ltcF90ZW1wKVtkZWdyZWUoZ19zaW1wX3RlbXApID09IDBdKQogICAgCiAgICBWKGdfc2ltcF90ZW1wKSROYW1lID0gVihnX3NpbXBfdGVtcCkkY29tcF90ZWFtCiAgCiAgICBuZXRfbGlzdFtbal1dID0gZ19zaW1wX3RlbXAKICAgIAogICAgaiA9IGogKyAxCiAgICAKICAgIHBsb3ROZXR3b3JrKGdfc2ltcF90ZW1wLCBtYWluID0gaSwgY29tcyA9IFYoZ19zaW1wX3RlbXApJGluZGV4KQogIH0KfQoKYGBgCgpEZW5zaXR5IG9yZGVyIG5ldHdvcmtzCgpgYGB7cn0KCgpwZGYoJ3Bsb3RDaGFubmVsTmV0d29ya3Nfb3JkZXJlZC5wZGYnKQpwYXIobWZyb3c9YygyLDIpLCBtYXI9YygwLDAsMiwwKSwgY2V4Lm1haW49MykKCm9yZGVyZWQgPSBvcmRlcihzYXBwbHkobmV0X2xpc3QsIGVkZ2VfZGVuc2l0eSkpCgpmb3IgKGkgaW4gb3JkZXJlZCkKewogIGcgPSBuZXRfbGlzdFtbaV1dCiAgCiAgcGxvdE5ldHdvcmsoZywgbWFpbiA9IHRlYW1zW2ldLCBjb21zID0gVihnKSRpbmRleCkKICAKfQoKYGBgCgoKSW50ZXJhY3Rpb25zIHdpdGggT3JnLiBUZWFtCgpgYGB7cn0KCmRmX29yZ19pbnQgPSBkYXRhLmZyYW1lKCkKCmZvciAoaSBpbiB1bmlxdWUoZGZfdG90YWwkQ2hhbm5lbCkpCnsKICBzdWJzID0gZGZfdG90YWxbZGZfdG90YWwkQ2hhbm5lbCA9PSBpLF0KICAKICBwYXJ0ID0gdXNlcnNfZGYkaWRbIWdyZXBsKCJEcm9wcGVkIE91dCIsIHVzZXJzX2RmJFRlYW0pXQogIAogIG4gPSBucm93KHN1YnNbc3VicyRUbyAlaW4lIHVzZXJzX2RmJGlkW3VzZXJzX2RmJFRlYW0gJWluJSBjKCJNZW50b3IiLCAiT3JnYW5pc2VyIiwgIlByb2plY3QgUGFydG5lciIpXSAmICFzdWJzJEZyb20gJWluJSB1c2Vyc19kZiRpZFt1c2Vyc19kZiRUZWFtICVpbiUgYygiTWVudG9yIiwgIk9yZ2FuaXNlciIsICJQcm9qZWN0IFBhcnRuZXIiKV0sXSkKICAKICBuMSA9IG5yb3coc3Vic1shc3VicyRUbyAlaW4lIHVzZXJzX2RmJGlkW3VzZXJzX2RmJFRlYW0gJWluJSBjKCJNZW50b3IiLCAiT3JnYW5pc2VyIiwgIlByb2plY3QgUGFydG5lciIpXSAmIHN1YnMkRnJvbSAlaW4lIHVzZXJzX2RmJGlkW3VzZXJzX2RmJFRlYW0gJWluJSBjKCJNZW50b3IiLCAiT3JnYW5pc2VyIiwgIlByb2plY3QgUGFydG5lciIpXSxdKQogIAogIGRmX29yZ19pbnQgPSByYmluZChkZl9vcmdfaW50LCBkYXRhLmZyYW1lKGNoYW5uZWwgPSBpLCBtZXNzYWdlc19vcmcgPSBuK24xKSkKICAKfQoKYGBgCgpQbG90CgpgYGB7cn0KCnBsdCA9IGdncGxvdChkZl9vcmdfaW50W2RmX29yZ19pbnQkY2hhbm5lbCAlaW4lIHRlYW1zLF0sIGFlcyh4ID0gcmVvcmRlcihjaGFubmVsLCBtZXNzYWdlc19vcmcpLCB5ID0gbWVzc2FnZXNfb3JnKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyB0aGVtZV9idyhiYXNlX3NpemUgPSAyMCkgKyB4bGFiKCJUZWFtcyIpICsgeWxhYigiIyBJbnRlcmFjdGlvbnMgd2l0aCBPcmcgVGVhbSIpICsgZ2d0aXRsZSgiSW50ZXJhY3Rpb25zIHdpdGggT3JnYW5pc2luZyBUZWFtIikgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsaGp1c3Q9MSx2anVzdD0xKSkgKyBjb29yZF9mbGlwKCkKCmdncGxvdGx5KHBsdCkKCmdnc2F2ZSgndml6L2ludGVyYWN0aW9uc19vcmdfdGVhbS5wZGYnLCB3aWR0aCA9IDgsIGhlaWdodCA9IDYpCgpgYGAKCioqKioqKioqKioqKioqKioqKioKCkNhbWlsbGUncyBDb2RlCgpgYGB7cn0KbGlicmFyeSgiZ3RzdW1tYXJ5IikKCmNvdW50cnkgJT4lCiAgc2VsZWN0KEdlbmRlciwgQWdlKSAlPiUKICB0Ymxfc3VtbWFyeSgKICAgIHN0YXRpc3RpYyA9IGxpc3QoYWxsX2NvbnRpbnVvdXMoKSB+ICJ7bWVkaWFufSAoe21pbn0sIHttYXh9KSIsCiAgICAgICAgICAgICAgICAgICAgIGFsbF9jYXRlZ29yaWNhbCgpIH4gIntufSAoe3B9JSkiKSkKCiNnZ3NhdmUoJ3Zpei9nZW5kZXJfdGJsX3N1bW1hcnkucGRmJywgd2lkdGggPSA2LCBoZWlnaHQgPSA1KQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY291bnRyeSkrCiAgYWVzKHggPSByZW9yZGVyKE5hdGlvbmFsaXR5LCBOYXRpb25hbGl0eSwgZnVuY3Rpb24oeCkgbGVuZ3RoKHgpKSkgKwogIGdlb21fYmFyKHN0YXQ9ImNvdW50Iix3aWR0aCA9IDAuOSwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgpKSsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxMCksIGJyZWFrcyA9IGMoMCwyLDQsNiw4LDEwKSkrCiAgbGFicyh5ID0gIkNvdW50Iix4ID0gIk5hdGlvbmFsaXR5IikrCiAgdGhlbWVfYncoKSArIGdlb21fdGV4dChzdGF0ID0gImNvdW50IiwgYWVzKGxhYmVsID0gIiIpLAogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMC45KSwgc2l6ZSA9IDIuNSwgaGp1c3QgPSAtMC41KSsKICBjb29yZF9mbGlwKCkKCmdnc2F2ZSgndml6L05hdGlvbmFsaXR5LnBkZicsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNSkKYGBgICAKCmBgYHtyfQoKZ2dwbG90KGNvdW50cnkpKwogIGFlcyh4ID0gcmVvcmRlcihDb3VudHJ5Lm9mLlJlc2lkZW5jZSwgQ291bnRyeS5vZi5SZXNpZGVuY2UsIGZ1bmN0aW9uKHgpIGxlbmd0aCh4KSkpICsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsd2lkdGggPSAwLjksIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoKSkrCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMTApLCBicmVha3MgPSBjKDAsMiw0LDYsOCwxMCkpKwogIGxhYnMoeSA9ICJDb3VudCIseCA9ICJDb3VudHJ5IG9mIG9yaWdpbiIpKwogIHRoZW1lX2J3KCkgKyBnZW9tX3RleHQoc3RhdCA9ICJjb3VudCIsIGFlcyhsYWJlbCA9ICIiKSwKICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuOSksIHNpemUgPSAyLjUsIGhqdXN0ID0gLTAuNSkrCiAgY29vcmRfZmxpcCgpCgpnZ3NhdmUoJ3Zpei9Db3VudHJ5X29mX29yaWdpbi5wZGYnLCB3aWR0aCA9IDYsIGhlaWdodCA9IDUpCmBgYAoKRWNvbHV0aW9uIGhhcyBvbmUgbW9yZSBtZW1iZXIgdGhhbiB0aGUgcGxvdCBpbiB0aGUgcmVwb3J0LiBUaGF0IGlzIGJlY2F1c2Ugb2Ygb25lIHVzZXIgd2hvIGxpc3RlZCB0aGVpciBvd24gbmFtZSBhcyB0aGUgdGVhbSBuYW1lLiBJIG1hdGNoZWQgdGhlIHVzZXIgd2l0aCB0aGVpciBjb3JyZXNwb25kaW5nIHRlYW0gLSBFY29sdXRpb24uIAoKYGBge3J9CmdncGxvdChjb3VudHJ5KSsKICBhZXMoeCA9IHJlb3JkZXIoVGVhbS5OYW1lLCBUZWFtLk5hbWUsIGZ1bmN0aW9uKHgpIGxlbmd0aCh4KSkpICsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsd2lkdGggPSAwLjksIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoKSkrCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgNSksIGJyZWFrcyA9IGMoMCwxLDIsMyw0LCA1KSkrCiAgbGFicyh5ID0gIkNvdW50Iix4ID0gIlRlYW1zIikrCiAgdGhlbWVfYncoKSArIGdlb21fdGV4dChzdGF0ID0gImNvdW50IiwgYWVzKGxhYmVsID0gIiIpLAogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMC45KSwgc2l6ZSA9IDIuNSwgaGp1c3QgPSAtMC41KSsKICBjb29yZF9mbGlwKCkKCmdnc2F2ZSgndml6L1RlYW1fc2l6ZS5wZGYnLCB3aWR0aCA9IDYsIGhlaWdodCA9IDUpCmBgYAoKYGBge3J9Cgpjb3VudHJ5ICU+JQogIGdncGxvdCggYWVzKHg9R2VuZGVyLCB5PUFnZSwgZmlsbD1HZW5kZXIpKSArCiAgZ2VvbV9ib3hwbG90KCkrCiAgc2NhbGVfZmlsbF9icmV3ZXIoKSsKICBnZW9tX2ppdHRlcihjb2xvcj0iYmxhY2siLCBzaXplPTAuNCwgYWxwaGE9MC45KSsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gYygxMCwgMTUsIDIwLCAyNSwgMzAsIDM1LCA0MCwgNDUpKSsKICBsYWJzKHggPSAiIiwgeT0iQWdlIiwgZmlsbCA9ICJHZW5kZXIiKSsKICB0aGVtZV9idygpCgpnZ3NhdmUoJ3Zpei9HZW5kZXJfQWdlLnBkZicsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNSkKYGBgCgo=